#include <pthread.h>
#include <sys/ioctl.h>
#include <getopt.h>

#include "input_yuyv.h"
#include "../../capture.h"
#include "../../utils.h"

//extern GLOBALS global;
GLOBALS *pGlobal;

#define BUF_NUM 5
#define DEVICE_NAME "/dev/video0"
#define INPUT_PLUGIN_NAME "yuyv_video_capture."
#define MAX_ARGUMENTS 30

BUFFER_A buffer_a[BUF_NUM];
int fd;
pthread_t worker;

JPEG_BUFFERS jpeg_buffers[5];

static const struct {
    const char *string;
    const int width, height;
} resolutions[] = {
    { "QSIF", 160,  120  },
    { "QCIF", 176,  144  },
    { "CGA",  320,  200  },
    { "QVGA", 320,  240  },
    { "CIF",  352,  288  },
    { "VGA",  640,  480  },
    { "SVGA", 800,  600  },
    { "XGA",  1024, 768  },
    { "SXGA", 1280, 1024 }
};


INPUT_INFO input_info;

static void GetPrintCap(void)
{
    int ret;
    struct v4l2_capability cap;
    ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
    if(ret < 0)
    {
        perror("get video device capbility");
        exit(EXIT_FAILURE);
    }
    if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0)
    {
        fprintf(stderr, "video capture not supported.\n");
        exit(EXIT_FAILURE);
    }

    if (!(cap.capabilities & V4L2_CAP_STREAMING))
    {
        fprintf(stderr, "does not support streaming i/o\n");
        exit(EXIT_FAILURE);
    }
    /* else
       {
       if (!(vd->cap.capabilities & V4L2_CAP_READWRITE))
       {
       fprintf(stderr, "%s does not support read i/o\n", vd->videodevice);
       goto fatal;
       }
       }
    */
    
    printf("========devices capability========\n");
    printf("driver: %s\n", cap.driver);
    printf("card: %s\n", cap.card);
    printf("bus_info: %s\n", cap.bus_info);
    printf("version: %u.%u.%u\n", (cap.version>>16)&0xFF, (cap.version>>8)& 0xFF, cap.version & 0xFF);
    printf("capabilities: %08X\n", cap.capabilities);
    
    
    struct v4l2_cropcap cropcap;
    struct v4l2_crop crop;
    if (0 == ioctl(fd, VIDIOC_CROPCAP, &cropcap)) {  
        crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
        crop.c.left = cropcap.defrect.left;  
        crop.c.top = cropcap.defrect.top;  
        crop.c.width = input_info.frameWidth;  
        crop.c.height = input_info.frameHeight;  
        printf("----->has ability to crop!!\n");  
        printf("cropcap.defrect = (%d, %d, %d, %d)\n", cropcap.defrect.left,  
               cropcap.defrect.top, cropcap.defrect.width,  
               cropcap.defrect.height);  
        if (-1 == ioctl(fd, VIDIOC_S_CROP, &crop)) {  
            switch (errno) {  
            case EINVAL:  
                /* Cropping not supported. */  
                break;  
            default:  
                /* Errors ignored. */  
                break;  
            }  
            printf("-----!!but crop to (%d, %d, %d, %d) Failed!!\n",  
                   crop.c.left, crop.c.top, crop.c.width, crop.c.height);  
        } else {  
            printf("----->sussess crop to (%d, %d, %d, %d)\n", crop.c.left,  
                   crop.c.top, crop.c.width, crop.c.height);  
        }  
    } else {  
        /* Errors ignored. */  
        printf("!! has no ability to crop!!\n");  
    }  
}

static void SetFrameFormat(void)
{
    int ret;
    struct v4l2_format format;
    format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    format.fmt.pix.width = input_info.frameWidth;
    format.fmt.pix.height = input_info.frameHeight;
    format.fmt.pix.pixelformat = input_info.format;
    format.fmt.pix.field = V4L2_FIELD_NONE;  // V4L2_FIELD_INTERLACED;
    
    ret = ioctl(fd, VIDIOC_S_FMT, &format);
    if(ret < 0)
    {
        perror("vidioc_s_fmt");
        exit(EXIT_FAILURE);
    }
    else
    {
        printf("succesed to set frame format.\n");
    }
}

static void GetPrintFrameFormat(void)
{
    int ret;
    struct v4l2_format format;
    format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_G_FMT, &format);
    if(ret < 0)
    {
        perror("vidioc_g_fmt");
        exit(EXIT_FAILURE);
    }
    printf ("current frame format is :\n");
    printf("\ttype: %d\n",format.type);
    printf("\twidth: %d\t",format.fmt.pix.width);
    printf("\theight: %d\n",format.fmt.pix.height);
    char str[8];
    memset(str, 0, 8);
    memcpy(str, &format.fmt.pix.pixelformat, 4);
    printf("\tpixelformat: %s\n",str);
    printf("\tfield: %d\n", format.fmt.pix.field);
    printf("\tbytesperline: %d\n", format.fmt.pix.bytesperline);
    printf("\tsizeimage: %d\n", format.fmt.pix.sizeimage);
    printf("\tcolorspace: %d\n", format.fmt.pix.colorspace);
    printf("\tpriv: %d\n", format.fmt.pix.priv);
    //    printf("\traw_date: %s\n", format.fmt.raw_data);
}

static void SetFrameRate(void)
{
    int ret;
    struct v4l2_streamparm setfps;
    memset(&setfps, 0, sizeof(struct v4l2_streamparm));
    setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    setfps.parm.capture.timeperframe.numerator = 1;
    setfps.parm.capture.timeperframe.denominator = input_info.frameRate;  //设置帧率
    ret = ioctl(fd, VIDIOC_S_PARM, &setfps);
    if(ret < 0)
    {
        perror("set frame rate");
        exit(EXIT_FAILURE);
    }
}

static void RequestBuffer(void)
{
    int ret;
    struct v4l2_requestbuffers reqbuf;
    memset(&reqbuf, 0, sizeof(reqbuf));
    reqbuf.count = BUF_NUM;
    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuf.memory = V4L2_MEMORY_MMAP;
    ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuf);
    if(ret < 0)
    {
        perror("request buffer");
        exit(EXIT_FAILURE);
    }
}

static void MapMemEnqueueBuf(void)
{
    int ret;
    int index = 0;
    struct v4l2_buffer querybuf;
    for(; index < BUF_NUM - 1; index++)
    {
        memset(&querybuf, 0, sizeof(querybuf));
        querybuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        querybuf.memory = V4L2_MEMORY_MMAP;
        querybuf.index = index;
        ret = ioctl(fd, VIDIOC_QUERYBUF, &querybuf);
        if(ret < 0)
        {
            perror("query buffer");
            exit(EXIT_FAILURE);
        }

        buffer_a[index].bufStart = mmap(NULL,
                                        querybuf.length,
                                        PROT_READ | PROT_WRITE,
                                        MAP_SHARED,
                                        fd,
                                        querybuf.m.offset);
        buffer_a[index].length = querybuf.length;

        //----------malloc memory for buffers which store JPEG picture-----
        //       jpeg_buffers[index].bufStart = (unsigned char *) malloc(querybuf.length);
        //--------------------------------------
        if (buffer_a[index].bufStart == MAP_FAILED)
        {
            perror("mmap");
            exit(EXIT_FAILURE);
        }
        
        ret = ioctl(fd, VIDIOC_QBUF, &querybuf);
        if(ret < 0)
        {
            perror("qbuf");
            exit(EXIT_FAILURE);
        }
    }
    pGlobal->buf = (unsigned char *) malloc(querybuf.length);  
}

static void *worker_thread( void *arg )
{
    while(!pGlobal->stopFlag)
    {
        struct v4l2_buffer querybuf;
        querybuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
        querybuf.memory = V4L2_MEMORY_MMAP;  
        if (-1 == ioctl(fd, VIDIOC_DQBUF, &querybuf))
        {  
            switch (errno)
            {  
            case EAGAIN:  
                fprintf(stderr, "eagain\n\n");
                usleep(1000);
                continue;
            case EIO:  
                fprintf(stderr, "eio\n\n");
                
            default:  
                fprintf(stderr, "default\n\n");
            }  
        }  
       
        pthread_mutex_lock(&pGlobal->db);
	if(pGlobal->avi_mode)
	{
	  memcpy(pGlobal->buf, buffer_a[querybuf.index].bufStart, buffer_a[querybuf.index].length);
	  pGlobal->bufSize = buffer_a[querybuf.index].length;
	  fprintf(stderr, "copy frame buf(%dKB) to global buf.\n", pGlobal->bufSize / 1024);
	}
	else
	{
	  int sizeOfJpeg = compress_yuyv_to_jpeg(buffer_a[querybuf.index].bufStart, pGlobal->buf, input_info.frameWidth * input_info.frameHeight,
						 input_info.quality, input_info.frameWidth, input_info.frameHeight);
	  pGlobal->bufSize = sizeOfJpeg;
	}
        		
        pthread_cond_broadcast(&pGlobal->db_update);
        pthread_mutex_unlock(&pGlobal->db );
	
        //buffer_enqueue(i);
        if (-1 == ioctl(fd, VIDIOC_QBUF, &querybuf))
        {
            exit(EXIT_FAILURE);
        }
        //usleep(10*1000);
    }    
    return ( (void*)0 );
}

void help(void) {
    fprintf(stderr, " ---------------------------------------------------------------\n" \
            " Help for input plugin..: "INPUT_PLUGIN_NAME"\n" \
            " ---------------------------------------------------------------\n" \
            " The following parameters can be passed to this plugin:\n\n" \
            " [-d | --device ].......: video device to open (your camera)\n" \
            " [-r | --resolution ]...: the resolution of the video device,\n" \
            "                          can be one of the following strings:\n" \
            "                          ");

    int i;  
    for ( i=0; i < LENGTH_OF(resolutions); i++ ) {
        fprintf(stderr, "%s ", resolutions[i].string);
        if ( (i+1)%6 == 0)
            fprintf(stderr, "\n                          ");
    }
    
    fprintf(stderr, "\n                          or a custom value like the following" \
            "\n                          example: 640x480\n");

    fprintf(stderr, " [-f | --fps ]..........: frames per second\n" \
            " [-y | --yuv ]..........: enable YUYV format and disable MJPEG mode\n" \
            " [-q | --quality ]......: JPEG compression quality in percent \n" \
            "                          (activates YUYV format, disables MJPEG)\n" \
            " ---------------------------------------------------------------\n\n");
}


static void ParseCmdLine(char *inputParam)
{
    char *argv[MAX_ARGUMENTS]={NULL}, *s;
    int argc=1, width=640, height=480;
    int i;
    
    char *str = strdup( strchr(inputParam, ' ') );
    // convert the single parameter-string to an array of strings 
    argv[0] = INPUT_PLUGIN_NAME;
    if ( str != NULL && strlen(str) != 0 ) {
        char *arg=NULL, *saveptr=NULL, *token=NULL;

        arg=(char *)strdup(str);

        if ( strchr(arg, ' ') != NULL ) {
            token=strtok_r(arg, " ", &saveptr);
            if ( token != NULL ) {
                argv[argc] = strdup(token);
                argc++;
                while ( (token=strtok_r(NULL, " ", &saveptr)) != NULL ) {
                    argv[argc] = strdup(token);
                    argc++;
                    if (argc >= MAX_ARGUMENTS) {
                        fprintf(stderr, "ERROR: too many arguments to input plugin\n");
                        exit(EXIT_FAILURE);
                    }
                }
            }
        }
    }
  

    // show all parameters for DBG purposes 
    for (i=0; i<argc; i++) {
        fprintf(stderr, "argv[%d]=%s\n", i, argv[i]);
    }
  

    /* parse the parameters */
    reset_getopt();
    while(1) {
        int option_index = 0, c=0;
        static struct option long_options[] = \
            {
                {"h", no_argument, 0, 0},
                {"help", no_argument, 0, 0},
                {"d", required_argument, 0, 0},
                {"device", required_argument, 0, 0},
                {"r", required_argument, 0, 0},
                {"resolution", required_argument, 0, 0},
                {"f", required_argument, 0, 0},
                {"fps", required_argument, 0, 0},
                {"y", no_argument, 0, 0},
                {"yuv", no_argument, 0, 0},
                {"q", required_argument, 0, 0},
                {"quality", required_argument, 0, 0},
                {0, 0, 0, 0}
            };

        /* parsing all parameters according to the list above is sufficent */
        c = getopt_long_only(argc, argv, "", long_options, &option_index);

        /* no more options to parse */
        if (c == -1) break;

        /* unrecognized option */
        if (c == '?'){
            help();
            //return 1;
            exit(EXIT_FAILURE);
        }

        /* dispatch the given options */
        switch (option_index) {
            /* h, help */
        case 0:
        case 1:
            help();
            exit(EXIT_SUCCESS);
            break;

            /* d, device */
        case 2:
        case 3:
            input_info.deviceName = strdup(optarg);
            break;

            /* r, resolution */
        case 4:
        case 5:
            //DBG("case 4,5\n");
            width = -1;
            height = -1;

            //try to find the resolution in lookup table "resolutions" 
            for ( i=0; i < LENGTH_OF(resolutions); i++ ) {
                if ( strcmp(resolutions[i].string, optarg) == 0 ) {
                    input_info.frameWidth  = resolutions[i].width;
                    input_info.frameHeight = resolutions[i].height;
                }
            }
        
            /* done if width and height were set */
            if(width != -1 && height != -1)
            {
                break;
            }
            /* parse value as decimal value */
            input_info.frameWidth  = strtol(optarg, &s, 10);
            input_info.frameHeight = strtol(s+1, NULL, 10);
            break;

            /* f, fps */
        case 6:
        case 7:        
            input_info.frameRate = atoi(optarg);
            break;

            /* y, yuv */
        case 8:
        case 9:
            input_info.format = V4L2_PIX_FMT_YUYV;
            break;

            /* q, quality */
        case 10:
        case 11:
            input_info.format = V4L2_PIX_FMT_YUYV;
            input_info.quality = MIN(MAX(atoi(optarg), 0), 100);
            break;

        default:
            help();
            exit(EXIT_FAILURE);
        }
    }
}

int input_init(char *inputParam, void *pglobal)
{
    pGlobal = (GLOBALS*) pglobal;
    
    input_info.deviceName = DEVICE_NAME;
    input_info.frameWidth = 640;
    input_info.frameHeight = 480;
    input_info.frameRate = 10;
    input_info.quality = 100;
    input_info.format = V4L2_PIX_FMT_YUYV;
    
    ParseCmdLine(inputParam);
    
    fd = open(input_info.deviceName, O_RDWR);
    if(-1 == fd)
    {	
        perror("open video device");
        exit(EXIT_FAILURE);
    }
    
    
    //--------------get and print device capbility------------------
    GetPrintCap();
    
    //------------------set frame format------------------------------
    SetFrameFormat();

    //-------------------get and print frame format------------------
    GetPrintFrameFormat();

    //-------------------set frame rate--------------------------
    SetFrameRate();
    
    //------------------request frame buffer-----------------------
    RequestBuffer();

    //-----------------query buffer and map memory and buffer enqunee------------------
    MapMemEnqueueBuf();
    
    printf("completed to init video device, waiting to run.\n");
    return 0;
}

int input_stop(void)
{    
    //pGlobal->stopFlag = 1;
  //等待线程退出
    int terr;
    void * tret;
    terr = pthread_join(worker, &tret);
    if(terr != 0)
    {
      perror("join thread input_yuyv worker_thread error!");
      exit(EXIT_FAILURE);
    }
    
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    //-----------stream off--------------
    int ret = ioctl(fd, VIDIOC_STREAMOFF,  &type);
    if(ret < 0)
    {
        perror("stream off");
        exit(EXIT_FAILURE);
    }
    
    //unmap buffer
    int i = 0;
    for(; i < BUF_NUM; i++)
    {
        munmap(buffer_a[i].bufStart, buffer_a[i].length);
    }
    close(fd);
    free(pGlobal->buf);
    return 0;
}

int input_run(void)
{
    //-----------------stream on--------------------
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    int ret = ioctl(fd, VIDIOC_STREAMON, &type);
    if(ret < 0)
    {
        perror("stream on");
        exit(EXIT_FAILURE);
    }

    if( pthread_create(&worker, 0, worker_thread, NULL) != 0)
    {
        //free(global->buf);
        fprintf(stderr, "could not start worker thread\n");
        exit(EXIT_FAILURE);
    }
    
    //使用了 pthread_join， 固不再使用线程分离
    //pthread_detach(worker);
    
    return 0;
}
